/**
 * 
 */
package ro.dta.idbi.ui.patterns;

import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import ro.dta.idbi.interfaces.sql.ISqlColumn;
import ro.dta.idbi.interfaces.sql.ISqlConstruct;
import ro.dta.idbi.interfaces.sql.ISqlForeignKey;
import ro.dta.idbi.interfaces.sql.ISqlTable;
import ro.dta.idbi.interfaces.ui.IMainFrame;
import ro.dta.idbi.model.AutoMedUtils;
import ro.dta.idbi.model.QueryResult;
import ro.dta.idbi.model.TransformationException;
import ro.dta.idbi.patterns.ColGener;
import ro.dta.idbi.ui.sql.SqlColumnCell;
import ro.dta.idbi.ui.sql.SqlForeignKeyCell;
import ro.dta.idbi.ui.sql.SqlGraph;
import ro.dta.idbi.ui.sql.SqlTableCell;
import uk.ac.bbk.dcs.automed.qproc.iql.ASG;
import uk.ac.bbk.dcs.automed.qproc.iql.Cell;
import uk.ac.ic.doc.automed.reps.Schema;

import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;

/**
 * UI for the <i>mandatory column and total generalisation pattern</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class ColGenerDialog extends AbstractPatternDialog {

	private static final long serialVersionUID = -6341017993230817910L;

	private final static Logger LOGGER = Logger
			.getLogger("com.dta.idbi.patterns.ui.ColGeneralisationDialog");

	/**
	 * Maximum no. of distinct values that a column can hold for this pattern to
	 * be applied
	 */
	private final static int TABLE_THRESHOLD = 4;

	/**
	 * Check if the current pattern can be applied over a given set of objects
	 * 
	 * Used to check if the items selected by the user in the UI can be used to
	 * apply this transformation pattern over them
	 * 
	 * @param schema
	 *            Schema in the context of which to check the validity of the
	 *            array of objects
	 * @param objects
	 *            Set of objects
	 * @return True if pattern can be applied over set of objects, false
	 *         otherwise
	 */
	public static boolean isValid(final Schema schema, final Object[] objects) {
		boolean result;
		if (objects.length == 1 && objects[0] instanceof ISqlColumn) {
			final ISqlColumn col = (ISqlColumn) objects[0];
			result = !col.isNullable() && !col.isPK();
		} else {
			result = false;
		}
		return result;
	}

	/**
	 * Distinct values stored in the column
	 */
	private transient List<String> colVals;

	/**
	 * Name of the newly created tables
	 */
	private transient List<String> tableNames;

	/**
	 * Newly created tables
	 */
	private transient List<ISqlTable> newTables;

	/**
	 * Newly created foreign keys contraints
	 */
	private transient List<ISqlForeignKey> newFKs;

	/**
	 * Name of the parent table containing the column
	 */
	private transient String parentName;

	/**
	 * Transformed graph component
	 */
	private transient mxGraphComponent transDiagram;

	/**
	 * Transformed graph
	 */
	private EditableGraph transGraph;

	/**
	 * Parent table in the transformed diagram
	 */
	private SqlTableCell newTable;

	/**
	 * Hierarchical layout used to display the transformed diagram
	 */
	private mxHierarchicalLayout layout;

	/**
	 * Distinct values stored in the column being generalised
	 */
	private ArrayList<Cell> distinctVals;

	/**
	 * Mandatory column and total generalisation dialog class constructor
	 * 
	 * @param parentFrame
	 *            Parent frame of this dialog
	 * @param schema
	 *            Schema over which the transformation is applied
	 * @param element
	 *            Sql construct(s) over which the transformation is applied
	 * @param beforeLabel
	 *            Label for the before diagram
	 * @param afterLabel
	 *            Label for the transformed diagram
	 */
	public ColGenerDialog(final IMainFrame parentFrame, final Schema schema,
			final ISqlConstruct[] element, final JLabel beforeLabel, final JLabel afterLabel) {
		super(parentFrame, schema, element, beforeLabel, afterLabel);

		if (!isValid(schema, element)) {
			throw new IllegalArgumentException(
					"This pattern can't be applied to the given elements");
		}

		setSize(800, 600);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.ui.patterns.TransformationDialog#createOriginalDiagram()
	 */
	@Override
	protected mxGraphComponent createOriginalDiagram() {
		final SqlGraph graph = new SqlGraph();
		graph.getModel().beginUpdate();

		try {
			final SqlTableCell parentTable = (SqlTableCell) ((SqlColumnCell) element[0])
					.getParent();
			final SqlTableCell newTable = (SqlTableCell) parentTable.clone();
			final ISqlColumn col = newTable.getColumn(element[0].getValue().toString());
			col.setStyle("SEL_COL");

			final mxGeometry newGeometry = new mxGeometry(5, 5,
					element[0].getGeometry().getWidth(), element[0].getGeometry().getHeight());
			newTable.setGeometry(newGeometry);
			newTable.layout(graph);
		} catch (CloneNotSupportedException e) {
			LOGGER.log(Level.SEVERE, "Fatal error", e);
		} finally {
			graph.setAutoSizeCells(true);
			graph.getModel().endUpdate();
		}

		return new mxGraphComponent(graph);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ro.dta.idbi.ui.patterns.TransformationDialog#createTransformedDiagram()
	 */
	@Override
	protected JComponent createTransformedDiagram() {
		JPanel diagPanel = new JPanel(new BorderLayout());

		transDiagram = null;
		try {
			initialise();

			try {
				newTables = new ArrayList<ISqlTable>();
				newFKs = new ArrayList<ISqlForeignKey>();
				transGraph = new EditableGraph();
				transGraph.getModel().beginUpdate();

				try {
					final SqlTableCell parentTable = (SqlTableCell) ((SqlColumnCell) element[0])
							.getParent();
					newTable = (SqlTableCell) parentTable.clone();
					final ISqlColumn col = newTable.getColumn(element[0].getValue().toString());
					newTable.removeColumn(col);

					final mxGeometry newGeometry = new mxGeometry(5, 5, element[0].getGeometry()
							.getWidth(), element[0].getGeometry().getHeight());
					newTable.setGeometry(newGeometry);
					newTable.relayout(transGraph);

					for (String t : tableNames) {
						insertTable(t, t);
					}
				} catch (CloneNotSupportedException e) {
					LOGGER.log(Level.SEVERE, "Fatal exception", e);
				} finally {
					layout = new mxHierarchicalLayout(transGraph);
					layout.execute(transGraph.getDefaultParent());
					transGraph.getModel().endUpdate();
				}

				final mxGraphComponent graphComponent = new mxGraphComponent(transGraph);
				graphComponent.setEnterStopsCellEditing(true);
				graphComponent.addListener(mxEvent.LABEL_CHANGED, new mxIEventListener() {

					@Override
					public void invoke(final Object arg0, final mxEventObject arg1) {
						final Object cell = arg1.getProperty("cell");
						final int index = newTables.indexOf(cell);
						final String newName = ((ISqlTable) cell).getValue().toString();

						tableNames.set(index, newName);
						final String fkname = newName + "_isa_" + parentName + "_fk";
						newFKs.get(index).setValue(fkname);
						graphComponent.revalidate();
						graphComponent.repaint();
						updateMacro();
					}
				});
				transDiagram = graphComponent;
			} catch (Exception e) {
				LOGGER.log(Level.SEVERE, "Fatal error", e);
			}

			diagPanel.add(transDiagram, BorderLayout.CENTER);
			diagPanel.add(createBottomPanel(), BorderLayout.SOUTH);
		} catch (TransformationException e) {
			diagPanel.add(new JLabel("Pattern cannot be applied. The column contains more than "
					+ TABLE_THRESHOLD + " distinct values."), BorderLayout.CENTER);
			confirmBtn.setEnabled(false);
		}

		return diagPanel;
	}

	/**
	 * Insert a new table in the transformed diagram
	 * 
	 * @param tableName
	 *            Name of the table to insert in the transformed diagram
	 * @param value
	 *            Column value associated with the new table
	 */
	private void insertTable(String tableName, String value) throws CloneNotSupportedException {
		final SqlTableCell tableCell = new SqlTableCell(null, null, "", tableName, new mxGeometry(
				0, 0, 150, 30), "SEL_TABLE");
		newTables.add(tableCell);
		final String fkname = tableName + "_isa_" + parentName + "_fk";
		final SqlForeignKeyCell foreignKey = new SqlForeignKeyCell(null, null, "", fkname,
				tableCell, newTable, "FK_EDGE");
		newFKs.add(foreignKey);
		transGraph.addCell(foreignKey, transGraph.getDefaultParent());

		final List<ISqlColumn> cols = newTable.getColumns();
		for (ISqlColumn c : cols) {
			if (c.getStyle().equals("PRIMARYKEY")) {
				final ISqlColumn col1 = (ISqlColumn) c.clone();
				tableCell.addColumn(col1);
			}
		}

		tableCell.relayout(transGraph);
	}

	/**
	 * Create the bottom panel that allows the user to insert an extra table
	 * 
	 * @return Bottom panel that allows the user to insert an extra table
	 */
	private JPanel createBottomPanel() {
		JPanel bottomPanel = new JPanel(new GridBagLayout());

		GridBagConstraints constr = new GridBagConstraints();
		constr.fill = GridBagConstraints.BOTH;

		constr.gridwidth = 2;
		constr.gridx = 0;
		constr.gridy = 0;
		constr.weightx = 1.0;
		constr.weighty = 0.0;
		constr.ipady = 5;
		bottomPanel.add(parentFrame.getGuiFactory().createTitleLabel("Insert an extra table"),
				constr);

		JLabel newTblLbl = new JLabel("Name of new table:");
		constr.ipady = 0;
		constr.gridwidth = 1;
		constr.gridx = 0;
		constr.gridy = 1;
		constr.weightx = 0.3;
		constr.weighty = 0.0;
		bottomPanel.add(newTblLbl, constr);

		final JTextField newTblFld = new JTextField();
		constr.gridx = 1;
		constr.gridy = 1;
		constr.weightx = 0.7;
		constr.weighty = 0.0;
		bottomPanel.add(newTblFld, constr);

		JLabel newValLbl = new JLabel("Value associated with new table:");
		constr.gridx = 0;
		constr.gridy = 2;
		constr.weightx = 0.3;
		constr.weighty = 0.0;
		bottomPanel.add(newValLbl, constr);

		final JTextField newValFld = new JTextField();
		constr.gridx = 1;
		constr.gridy = 2;
		constr.weightx = 0.7;
		constr.weighty = 0.0;
		bottomPanel.add(newValFld, constr);

		JButton addBtn = new JButton("Insert new table");
		addBtn.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent paramActionEvent) {
				try {
					String newTbl = newTblFld.getText();
					String newVal = newValFld.getText();

					if (newTbl.equals("") && newVal.equals("")) {
						JOptionPane
								.showMessageDialog(
										ColGenerDialog.this,
										"The name of the new table and the value associated with it can't be empty",
										"Error", JOptionPane.ERROR_MESSAGE);
						return;
					}
					insertTable(newTbl, newVal);
					layout.execute(transGraph.getDefaultParent());
					tableNames.add(newTbl);
					colVals.add("'" + newVal + "'");
					newTblFld.setText("");
					newValFld.setText("");
					updateMacro();
				} catch (CloneNotSupportedException e) {
					LOGGER.log(Level.SEVERE, "Fatal error", e);
				}
			}
		});
		constr.fill = GridBagConstraints.NONE;
		constr.gridwidth = 2;
		constr.gridx = 0;
		constr.gridy = 3;
		constr.weightx = 1.0;
		constr.weighty = 0.0;
		bottomPanel.add(addBtn, constr);

		return bottomPanel;
	}

	/**
	 * Initialise pattern
	 */
	@SuppressWarnings("unchecked")
	private void initialise() throws TransformationException {
		final SqlTableCell parent = (SqlTableCell) ((SqlColumnCell) element[0]).getParent();
		parentName = parent.getValue().toString();

		colVals = new ArrayList<String>();
		tableNames = new ArrayList<String>();
		int crtTable = 1;

		// Get distinct values stored in the column
		final StringBuilder iql = new StringBuilder("distinct [ {y} | {x,y} <- <<");
		iql.append(parentName).append(",").append(element[0].getValue()).append(">> ]");

		QueryResult result;
		try {
			result = AutoMedUtils.runIqlQuery(schema, iql.toString());
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, "Fatal exception", e);
			return;
		}

		distinctVals = ASG.fromASGList(result.getAsg());
		if (distinctVals.size() > TABLE_THRESHOLD) {
			throw new TransformationException("This pattern can't be applied to the given elements");
		}

		for (Cell c : distinctVals) {
			final ArrayList<Cell> cols = ASG.fromASGTuple(c);
			if (cols.size() == 1) {
				final String crtVal = cols.get(0).toString();
				String name;
				if (cols.get(0).isString()) {
					name = crtVal.substring(1, crtVal.length() - 1);
				} else {
					name = "table" + (crtTable++);
				}
				tableNames.add(name);
				colVals.add(crtVal);
			}
		}

		updateMacro();
	}

	/**
	 * Update the macro field, with the command for the transformation
	 */
	private void updateMacro() {
		final StringBuilder valsList = new StringBuilder("{");
		final StringBuilder tableList = new StringBuilder("");

		for (int i = 0; i < tableNames.size(); i++) {
			tableList.append("<<");
			tableList.append(tableNames.get(i)).append(">>, ");
			valsList.append(colVals.get(i)).append(", ");
		}
		valsList.setLength(valsList.length() - 2);
		valsList.append("}");
		tableList.setLength(tableList.length() - 2);

		macroField.setText(ColGener.COMMAND + " (<<" + parentName + "," + element[0].getValue()
				+ ">>, " + tableList + ", " + valsList + ")");
	}

}
